#pragma once
#include "QueryPreprocessor.h"
#include "QueryCreator.h"
#include <regex>

QueryPreprocessor::QueryPreprocessor(std::string arg){
	query = arg;
	
	if(isSyntaxValid()){
		try{
			extractAllParameters();
		} catch(CustomException e){
			throw e;
		}
	} else {
		throw CustomException(PQL_PARSE_ERROR, "Invalid query syntax");
	}
}

//Check for the query syntactical validity.
bool QueryPreprocessor::isSyntaxValid(){
	std::tr1::regex select_rgx(select_cl);
	if(regex_match(query.begin(), query.end(), select_rgx)){
		return true;
	} else {
		return false;
	}
}

void QueryPreprocessor::extractAllParameters(){
	extractDeclarationParameters();
	extractSuchthatParameters();
	extractPatternParameters();
	extractResultParameter();
}

//Extract declaration clauses. e.g assign a1, a2; while w;
void QueryPreprocessor::extractDeclarationParameters(){
	std::tr1::regex regex(declaration);

	std::regex_iterator<std::string::iterator> regex_iterator(query.begin(), query.end(), regex);
	std::regex_iterator<std::string::iterator> rend;

	while (regex_iterator != rend) {
		std::string currentString = regex_iterator->str();
		std::tr1::regex de_regex(design_entity);
		std::tr1::regex syn_regex(synonym);
		std::regex_iterator<std::string::iterator> itr1(currentString.begin(), currentString.end(), de_regex);
		
		VAR_TYPE vt;
		std::string type = itr1->str();

		if(type == "stmt"){
			vt = Q_STATEMENT;
			currentString = currentString.substr(5,currentString.size()-5);
		} else if(type == "assign"){
			vt = Q_ASSIGN;
			currentString = currentString.substr(6,currentString.size()-6);
		} else if(type == "while"){
			vt = Q_WHILE;
			currentString = currentString.substr(5,currentString.size()-5);
		} else if(type == "variable"){
			vt = Q_VARIABLE;
			currentString = currentString.substr(8,currentString.size()-8);
		} else if(type == "constant"){
			vt = Q_CONSTANT;
			currentString = currentString.substr(8,currentString.size()-8);
		} else if(type == "prog_line"){
			vt = Q_PROGRAM_LINE;
			currentString = currentString.substr(9,currentString.size()-9);
		} else {
			throw CustomException(PQL_PARSE_ERROR, "Invalid declaration type");
		}

		std::regex_iterator<std::string::iterator> itr(currentString.begin(), currentString.end(), syn_regex);

		while(itr != rend){
			std::string varName = itr->str();
			if(declaration_parameters.count(varName) != 1){
				declaration_parameters.insert(std::pair<std::string,VAR_TYPE>(varName, vt));
			} else{
				throw CustomException(PQL_PARSE_ERROR, "Duplicate declarations found");
			}
			itr++;
		}

		regex_iterator++;
	}
}

//Extract relationship query. e.g Modifies(a,"x")
void QueryPreprocessor::extractSuchthatParameters(){
	std::tr1::regex regex(relRef);

	std::regex_iterator<std::string::iterator> regex_iterator(query.begin(), query.end(), regex);
	std::regex_iterator<std::string::iterator> rend;

	while (regex_iterator != rend) {
		std::string currentString = regex_iterator->str();
		std::tr1::regex mod_reg(Reg_ModifiesS);
		std::tr1::regex use_reg(Reg_UsesS);
		std::tr1::regex parent_reg(Reg_Parent);
		std::tr1::regex parentstar_reg(Reg_ParentT);
		std::tr1::regex follows_reg(Reg_Follows);
		std::tr1::regex followsstar_reg(Reg_FollowsT);
		std::tr1::smatch match;

		if(regex_match(currentString, match, mod_reg)){
			suchthat_parameters.push_back(std::make_tuple(MODIFIES, match[2], match[3]));
		} else if(regex_match(currentString, match, use_reg)){
			suchthat_parameters.push_back(std::make_tuple(USES, match[2], match[3]));
		} else if(regex_match(currentString, match, parent_reg)){
			suchthat_parameters.push_back(std::make_tuple(PARENT, match[2], match[3]));
		} else if(regex_match(currentString, match, parentstar_reg)){
			suchthat_parameters.push_back(std::make_tuple(PARENTSTAR, match[2], match[3]));
		} else if(regex_match(currentString, match, follows_reg)){
			suchthat_parameters.push_back(std::make_tuple(FOLLOWS, match[2], match[3]));
		} else if(regex_match(currentString, match, followsstar_reg)){
			suchthat_parameters.push_back(std::make_tuple(FOLLOWSSTAR, match[2], match[3]));
		} else {
			throw CustomException(PQL_PARSE_ERROR, "Invalid relation specification");
		}
		++regex_iterator;
	}
}

//Extract result parameter. e.g Select a1. a1 is the result parameter.
void QueryPreprocessor::extractResultParameter(){
	std::tr1::regex regex(result_cl);
	std::tr1::smatch match;

	regex_search(query, match, regex);
	result_parameter = match[2];
}

//Extract patterns clauses. e.g pattern a1(_,_"x"_)
void QueryPreprocessor::extractPatternParameters(){
	std::tr1::regex regex(pattern_cl);

	std::regex_iterator<std::string::iterator> regex_iterator(query.begin(), query.end(), regex);
	std::regex_iterator<std::string::iterator> rend;

	while (regex_iterator != rend) {
		std::string currentString = regex_iterator->str();
		std::tr1::smatch match;
		regex_search(currentString, match, regex);
		pattern_parameters.push_back(std::make_tuple(match[2], match[3], match[4]));
		++regex_iterator;
	}
}

bool QueryPreprocessor::isContentValid(){
	
	//If there is a pattern, check that the synonym is an assignment
	//i.e pattern a1(_,"x"); -> a1 has to be an assignment.
	std::tr1::regex pattern_rgx(pattern_cl);
	std::tr1::smatch matches;
	if(regex_search(query, matches, pattern_rgx)){
		if(declaration_parameters.find(matches[2])->second != Q_ASSIGN){
			return false;
		}
		if(declaration_parameters.find(matches[3]) != declaration_parameters.end() &&
			declaration_parameters.find(matches[3])->second != Q_VARIABLE){
				return false;
		}
	}

	/*
	within the "such that" subsection, all clauses are valid i.e.
	1)Modifies and Uses clauses: param1 is of VAR_TYPE STATEMENT / ASSIGN / WHILE / STMT_LITERAL, and param2 is of VAR_TYPE VAR_LITERAL / VARIABLE
	2)Parent and Parent* clauses: param1 is of VAR_TYPE STATEMENT / WHILE / STMT_LITERAL, and param2 is of VAR_TYPE STATEMENT / WHILE / ASSIGN / STMT_LITERAL
	3)Follows and Follows* clauses: param1 and param2 are of VAR_TYPE STATEMENT / WHILE / ASSIGN / STMT_LITERAL(i.e 1)
	Every parameter can also be declared as an ANY_VALUE type - EXCEPT the first parameter of Modifies and Uses.
	*/
	std::tr1::regex any_val(any_value);
	for(size_t i = 0; i < suchthat_parameters.size(); i++){
		std::tuple<QueryType, std::string, std::string> tuple = suchthat_parameters.at(i);
		QueryType qt = std::get<0>(tuple);
		std::string param1 = std::get<1>(tuple);
		std::string param2 = std::get<2>(tuple);
		VAR_TYPE param1_type;
		VAR_TYPE param2_type;
		//std::cout << param1 << " " << param2 << std::endl;
		if(declaration_parameters.find(param1) == declaration_parameters.end()){
			std::tr1::regex stmt_lit(stmt_literal);
			if(regex_match(param1.begin(), param1.end(), stmt_lit)){
				declaration_parameters.insert(std::pair<std::string,VAR_TYPE>(param1, Q_STMT_LITERAL));
			} //Modifies and Uses can't accept a free value for the first parameter
			else if(regex_match(param1.begin(), param1.end(), any_val) && qt != MODIFIES && qt != USES){
				//std::cout << "param1 ok" << std::endl;
				declaration_parameters.insert(std::pair<std::string,VAR_TYPE>(param1, Q_ANY_VALUE));
			} else{
				return false;
			}
		} else{
			param1_type = declaration_parameters.find(param1)->second;
			if(qt == MODIFIES || qt == USES || qt == FOLLOWS || qt == FOLLOWSSTAR){
				if(param1_type != Q_STATEMENT && param1_type != Q_ASSIGN && param1_type != Q_WHILE){
					return false;
				}
			} else if(qt == PARENT || qt == PARENTSTAR){
				if(param1_type != Q_STATEMENT && param1_type != Q_WHILE){
					return false;
				}
			}
		}

		if(declaration_parameters.find(param2) == declaration_parameters.end()){
			std::tr1::regex stmt_lit(stmt_literal);
			std::tr1::regex var_lit(var_literal);

			if(regex_match(param2.begin(), param2.end(), any_val)){
				//std::cout << "param2 ok" << std::endl;
				declaration_parameters.insert(std::pair<std::string,VAR_TYPE>(param2, Q_ANY_VALUE));
			} else{
				if(qt == MODIFIES || qt == USES){
					if(regex_match(param2.begin(), param2.end(), var_lit)){
						declaration_parameters.insert(std::pair<std::string,VAR_TYPE>(param2, Q_VAR_LITERAL));
					} else{
						return false;
					}
				} else if(regex_match(param2.begin(), param2.end(), stmt_lit)){
					declaration_parameters.insert(std::pair<std::string,VAR_TYPE>(param2, Q_STMT_LITERAL));
				} else {
					return false;
				}
			}
		} else{
			param2_type = declaration_parameters.find(param2)->second;
			if(qt == MODIFIES || qt == USES){
				if(param2_type != Q_VARIABLE){
					return false;
				}
			} else if(qt == FOLLOWS || qt == FOLLOWSSTAR || qt == PARENT || qt == PARENTSTAR){
				if(param2_type != Q_STATEMENT && param2_type != Q_ASSIGN && param2_type != Q_WHILE && param2_type != Q_ANY_VALUE){
					return false;
				}
			}
		}
	}
	return true;
}

CompleteQuery *QueryPreprocessor::buildQuery(){
	if(!isContentValid()){
		throw CustomException(PQL_PARSE_ERROR, "query is semantically invalid");
	}
	
	QueryCreator *queryCreator = QueryCreator::getQueryCreator();
	queryCreator->createNewQuery();

	//Populate Synonims from declarations
	for (std::map<std::string,VAR_TYPE>::iterator it = declaration_parameters.begin(); it != declaration_parameters.end(); ++it){
		std::string name = it->first;
		VAR_TYPE type = it->second;
		queryCreator->addSynonym(new Synonym(type, name));
	}

	//Populate Results
	VAR_TYPE result_type = declaration_parameters.at(result_parameter);
	queryCreator->addResult(new Result(result_type, result_parameter));

	//Populate the Query
	for(size_t i = 0; i < suchthat_parameters.size(); i++){
		std::tuple<QueryType, std::string, std::string> tuple = suchthat_parameters.at(i);
		QueryType qt = std::get<0>(tuple);
		std::string param1 = std::get<1>(tuple);
		std::string param2 = std::get<2>(tuple);
		VAR_TYPE param1_type = declaration_parameters.find(param1)->second;
		VAR_TYPE param2_type = declaration_parameters.find(param2)->second;
		queryCreator->addQuery(qt, new Synonym(param1_type, param1), new Synonym(param2_type,param2));
	}

	for(size_t i = 0; i < pattern_parameters.size(); i++){
		std::tuple<std::string, std::string, std::string> tuple = pattern_parameters.at(i);
		std::string param1 = std::get<0>(tuple);
		std::string param2 = std::get<1>(tuple);
		std::string param3 = std::get<2>(tuple);

		VAR_TYPE param2_type;
		if(declaration_parameters.find(param2) != declaration_parameters.end()){
			param2_type = declaration_parameters.find(param2)->second;
		} else{
			param2_type = Q_VAR_LITERAL;
		}

		queryCreator->addQuery(PATTERN, new Synonym(Q_ASSIGN, param1), new Synonym(param2_type, param2), new Synonym(Q_VAR_LITERAL, param3));
	}
	return queryCreator->getCompleteQuery();
}